Skip to content

Simplify media stream implementation#101

Merged
dy merged 24 commits into
masterfrom
mic
May 2, 2026
Merged

Simplify media stream implementation#101
dy merged 24 commits into
masterfrom
mic

Conversation

@dy
Copy link
Copy Markdown
Member

@dy dy commented Apr 29, 2026

Based on #35 (comment)

This is much closer to a standard implementation of MediaStreamTrack and MediaStream.

Here's why:

  1. EventTarget Extension: Standard web APIs like MediaStreamTrack and MediaStream inherit from EventTarget. Both classes now explicitly extend EventTarget.
  2. State Properties: MediaStreamTrack has standard read-only properties (id, kind, label, readyState) and enabled flag.
  3. Methods: Added standard clone(), getSettings(), stop(), getTracks(), getAudioTracks(), getVideoTracks(), addTrack(), removeTrack().
  4. Separation of Concerns: The stream data source logic (pushData) now lives appropriately on the MediaStreamTrack, mirroring how audio capture happens on a track level natively, rather than on the MediaStreamAudioSourceNode directly.

The tests run successfully with this refactored, more standards-compliant implementation.

Copy link
Copy Markdown
Collaborator

@k2d222 k2d222 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment, otherwise LGTM 👍

Comment thread examples/mic.js Outdated
await ctx.resume()

const src = new MediaStreamAudioSourceNode(ctx, { numberOfChannels: channels, bitDepth })
const track = new MediaStreamTrack('audio', 'mic', { channelCount: channels, sampleSize: bitDepth, sampleRate })
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes a lot more sense, but I would make this a subclass of MediaStreamTrack. In the WebAudio spec, MediaStreamTrack has no public constructor, and no pushData method. But you can freely extend the spec by creating a CustomMediaStreamTrack type, that has a public constructor and the push method. There is prior art with CanvasCaptureMediaStreamTrack.

For the parameters, I would provide just one in an object, because it's not clear what the first two parameters are for (track kind, and track label). In short:

const track = new CustomMediaStreamTrack({
  kind: 'audio',
  label: 'track from audio-mic',
  settings: { channelCount, sampleSize, bitDepth }
});

Copy link
Copy Markdown
Member Author

@dy dy Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, added.
Do you think CustomMediaStreamTrack is fine name? It's not immediately reflective of its purpose.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the media-stream side of the library to expose explicit MediaStream/MediaStreamTrack classes, move PCM ingestion onto a new CustomMediaStreamTrack, and align the microphone example/docs with that new flow. It fits into the codebase by reshaping how MediaStreamAudioSourceNode interoperates with Node-side audio capture.

Changes:

  • Add exported MediaStream, MediaStreamTrack, and CustomMediaStreamTrack classes plus TypeScript declarations.
  • Rework MediaStreamAudioSourceNode to read queued audio from a track instead of accepting pushData() directly.
  • Update the polyfill, tests, example microphone app, and README to use the new custom-track-based capture path.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
test/polyfill.test.js Updates polyfill tests for new globals and custom track behavior.
test/MediaStreamNodes.test.js Reworks media stream node tests to feed audio through CustomMediaStreamTrack.
src/MediaStreamAudioSourceNode.js Moves source-node input handling to track-backed buffers and keeps destination-node track creation in sync.
src/MediaStream.js Introduces the new MediaStream/track implementations and PCM normalization logic.
polyfill.js Simplifies globals installed by the Node polyfill.
index.js Exports the new media stream classes from the package root.
index.d.ts Updates public typings for the new media stream API surface.
examples/mic.js Migrates the microphone demo to push PCM into a custom track.
README.md Rewrites microphone capture guidance around CustomMediaStreamTrack.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/MediaStream.js Outdated
Comment thread polyfill.js
Comment thread src/MediaStreamAudioSourceNode.js Outdated
Comment thread src/MediaStream.js Outdated
Comment thread src/MediaStreamAudioSourceNode.js Outdated
dy and others added 2 commits May 1, 2026 20:09
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot finished work on behalf of dy May 2, 2026 00:14
@dy dy requested a review from Copilot May 2, 2026 00:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 10 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/MediaStream.js Outdated
Comment thread src/MediaStreamAudioSourceNode.js Outdated
Comment thread src/MediaStream.js Outdated
Comment thread src/MediaStream.js Outdated
Comment thread polyfill.js Outdated
Comment thread src/MediaStream.js Outdated
dy and others added 4 commits May 1, 2026 21:08
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot finished work on behalf of dy May 2, 2026 01:16
@dy dy requested a review from Copilot May 2, 2026 03:28
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 10 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/MediaStreamAudioSourceNode.js Outdated
Comment thread src/MediaStream.js
Comment thread src/MediaStream.js Outdated
Comment thread src/MediaStream.js
Comment thread test/polyfill.test.js
Comment thread polyfill.js Outdated
Comment thread src/MediaStreamAudioSourceNode.js
@dy
Copy link
Copy Markdown
Member Author

dy commented May 2, 2026

@copilot apply changes based on the comments in this thread

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 10 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread polyfill.js Outdated
Comment thread src/MediaStreamAudioSourceNode.js Outdated
Comment thread src/MediaStream.js Outdated
Comment thread test/polyfill.test.js Outdated
Comment thread test/MediaStreamNodes.test.js
dy and others added 4 commits May 2, 2026 00:04
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot finished work on behalf of dy May 2, 2026 04:11
@dy dy requested a review from Copilot May 2, 2026 04:38
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 10 changed files in this pull request and generated 7 comments.

Comments suppressed due to low confidence (1)

src/MediaStreamAudioSourceNode.js:115

  • createMediaStreamDestination().stream is still a plain object here instead of an instance of the new MediaStream class. That means destination streams do not participate in the EventTarget-based API added by this PR (instanceof MediaStream, stream events, etc.), so callers now get two incompatible "MediaStream" shapes depending on where the stream came from.
    this.#stream = {
      get _buffers() { return track._buffers },
      read() { return track._buffers.shift() || null },
      get readable() { return track._buffers.length > 0 },
      getTracks: () => [track],
      getAudioTracks: () => [track],
      getVideoTracks: () => [],
    }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/MediaStream.js Outdated
Comment thread README.md
Comment thread polyfill.js Outdated
Comment thread test/polyfill.test.js Outdated
Comment thread src/MediaStream.js
Comment thread src/MediaStream.js Outdated
Comment thread src/MediaStream.js Outdated
dy and others added 5 commits May 2, 2026 08:02
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 2, 2026

@copilot apply changes based on the comments in this thread

The last outstanding item from that review was the strong-reference ended listener in clone() (introduced after 526c715). Fixed in a2e5755: the listener that closed over clone directly has been removed. Per-spec, clones are independent tracks and callers stop them separately; _pushNormalized now lazily prunes clones whose readyState === 'ended', so explicitly-stopped clones no longer receive data and are removed from the fan-out set without needing any listener.

@dy
Copy link
Copy Markdown
Member Author

dy commented May 2, 2026

@copilot CI fails, please fix

… fix WPT silence-detector test

Agent-Logs-Url: https://github.com/audiojs/web-audio-api/sessions/0daa36f8-3469-425c-89fa-365d5fb40d2c

Co-authored-by: dy <300067+dy@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 2, 2026

@copilot CI fails, please fix

Fixed in 2515659. The WPT test mediastreamaudiosourcenode-from-context-with-different-rate was failing because MediaStreamAudioSourceNode._tick() returned a zero-filled buffer when the track ended but never set this._ended = true. AudioWorkletNode checks _ended on its sources before each quantum — only when _ended = true does it pass an empty inputs[0] to the worklet's process(), making hasInput = false. Added this._ended = false in the constructor and this._ended = true in _tick() when track.readyState === 'ended'.

Copilot finished work on behalf of dy May 2, 2026 13:39
@dy dy merged commit e162cbc into master May 2, 2026
@dy dy deleted the mic branch May 2, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants